<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="d3.js"></script>
    <script src="lodash.js"></script>
    <style> 
        html, body {
            height: 105%;
        }
        body {
            margin: 0; 
            background-color: #000;
        }
        .svg {
            height: 1000px;
            width: 512px;
            background-color: #f5f6f7;
            margin: 0 auto;
        }
        
    </style>
</head>
<body>
   <div class="svg">       
       <svg width="100%" height="100%" viewBox="0 0 512 1000"></svg>
   </div>
    
    <script>
        var NODE_TYPE = {
            OTHER      : 0,
            DEFAULT    : 1,
            SCAN_RANGE : 2,
            RECON      : 3,
            FEATURE    : 4
        };

        // coordinate.x of different type node
        var NODE_X = {
            DEFAULT    : 40,
            SCAN_RANGE : 150,
            RECON      : 270,
            FEATURE    : 390
        };

        var NODE_FILL = {
            DEFAULT    : "#CCC",
            SCAN_RANGE : "teal",
            RECON      : "red",
            FEATURE    : "blue"
        };
        
        var FIRST_Y = 60,
            OFFSET_Y = 100,
            R = 18;
        
        var initScanRange = function(nodes) {
            if (_.isArray(nodes)) {
                nodes.forEach(function(e, i) {
                    initScanRange(e);
                });
            } else {                
                nodes.type = NODE_TYPE.SCAN_RANGE;
                nodes.x = NODE_X.SCAN_RANGE;
                nodes.fill = NODE_FILL.SCAN_RANGE;
            }
        }
        
        var initRecon = function(nodes) {
            if (_.isArray(nodes)) {
                nodes.forEach(function(e, i) {
                    initRecon(e);
                });
            } else {                
                nodes.type = NODE_TYPE.RECON;
                nodes.x = NODE_X.RECON;
                nodes.fill = NODE_FILL.RECON;
            }
        }
        
        var initFeature = function(nodes) {
            if (_.isArray(nodes)) {
                nodes.forEach(function(e, i) {
                    initFeature(e);
                });
            } else {                
                nodes.type = NODE_TYPE.FEATURE;
                nodes.x = NODE_X.FEATURE;
                nodes.fill = NODE_FILL.FEATURE;
            }
        }
        
        var addNodes = function(parent, nodes) {
            for (var i = 0; i < nodes.length; i++) {
                var node = nodes[i];
                parent.children.push(node);
                node.parent = parent;
            }
        }
        
        // 把node定位到子节点的正中间
        var middleTheNode = function(node, recursive) {
            var children = node.children,
                len = children.length;
            
            if (len > 0) {
//                var y = 0;
//                children.forEach(function(e, i) {
//                    middleTheNode(e);
//                    y += e.y;
//                });
//                
//                node.y = y / len;
                
                for (var i = 0; i < len; ++i) {
                    if (recursive) {
                        middleTheNode(children[i], recursive);
                    }
                    node.y = (children[0].y + children[len - 1].y) / 2;
                }
            }
        }
        
       // var adjustScanRangeNode = function(node)
        
        // 获取 当前节点 势力范围 的最大Y值
        var getMaxYOfNode = function(node) {
            var children = node.children,
                len = children.length;
            
            // 最子一层
            if (len === 0) {
                return node.y;
            }
            
            // 有子节点, 取所有子节点中 y 的最大值
            var maxY = node.y;
            
            if (len > 0) {
                children.forEach(function(e, i) {
                    maxY = Math.max(maxY, getMaxYOfNode(e)); 
                });
            }
            
            return maxY;
        }
                
        var srIndex = 0,
            reconIndex = 0,
            featureIndex = 0;
        
        var locate = function(node) {
            var children = node.children,
                len = children.length;
            
            switch (node.type) {
                case NODE_TYPE.SCAN_RANGE:
                    node.y = (srIndex++) * OFFSET_Y + FIRST_Y;
                    break;
                case NODE_TYPE.RECON:
                    node.y = (reconIndex++) * OFFSET_Y + FIRST_Y;
                    break;
                case NODE_TYPE.FEATURE:
                    node.y = (featureIndex++) * OFFSET_Y + FIRST_Y;
                    break;
            }

            children.forEach(function(e, i) {
                locate(e);
            }); 
        }
        
        // 由树形节点创建连线
        var createLines = function(node, lines) {
            var children = node.children,
                len = children.length;
             
            children.forEach(function(e, i) {
                lines.push({
                    source: node,
                    target: e,
                    x1: node.x + R,
                    y1: node.y,
                    x2: e.x - R,
                    y2: e.y
                });
                
                createLines(e, lines);
            });
        }        
        var createLinesFromRoot = function(root, lines) {
            var children = root.children,
                len = children.length;
            
            var lines = [];
            children.forEach(function(e, i) {
                createLines(e, lines);
            });
            return lines;
        }
                
        function RxNode(name) {
            this.name = name;
            this.type = NODE_TYPE.DEFAULT;
            this.x = 0;
            this.y = 0;
            this.fill = NODE_FILL.DEFAULT;
            this.children = [];
            this.parent = null;
            this.maxY = 0;      // 节点及其所有子节点中的最大Y值
            this.nextY = 0;     // 用于定位 子节点 时的临时记录
        }
        
        var system = new RxNode("system");
        var root = new RxNode("root");
        
        var n1 = new RxNode("n1"),
            n2 = new RxNode("n2"),
            n3 = new RxNode("n3"),
            n4 = new RxNode("n4");
        
        var sr1 = new RxNode("sr1"),
            sr2 = new RxNode("sr2");
        
        var r1 = new RxNode("r1"),
            r2 = new RxNode("r2"),
            r3 = new RxNode("r3"),
            r4 = new RxNode("r4"),
            r5 = new RxNode("r5");
        
        var f1 = new RxNode("f1"),
            f2 = new RxNode("f2"),
            f3 = new RxNode("f3"),
            f4 = new RxNode("f4"),
            f5 = new RxNode("f5"),
            f6 = new RxNode("f6"),
            f7 = new RxNode("f7"),
            f8 = new RxNode("f8"),
            f9 = new RxNode("f9");
        
        initScanRange([sr1, sr2]);        
        initRecon([r1, r2, r3, r4, r5]);        
        initFeature([f1, f2, f3, f4, f5, f6, f7, f8, f9]);
        
        addNodes(system, [n1, n2, n3, n4]);
        addNodes(root, [sr1, sr2]);        
        addNodes(sr1, [r1, r2, r3, r4]);
        addNodes(sr2, [r5]);
        
        addNodes(r1, [f1, f2, f3, f4]);
        addNodes(r3, [f5, f6, f7]);
        addNodes(r4, [f8]); 
        addNodes(r5, [f9]);
        
        _.map(system.children, function(e, i) { 
                e.y = FIRST_Y + OFFSET_Y * i;
                e.x = NODE_X.DEFAULT;
            });
        
        
        // 开始定位 (root 是 series 节点, 虚拟的, 不显示)        
        root.nextY = root.maxY = root.y  = FIRST_Y;        
        locate(root);
        middleTheNode(root, true);
        
        // 生成连线
        var lines = createLinesFromRoot(root);        
        
        // 开始画图
        var drawing = function() {
            var svg = d3.select("svg");        
            var relations = svg.selectAll("line")
                .data(lines)
                .enter()
                .append("line")
                .attr({
                    x1: function(d) {return d.x1},
                    y1: function(d) {return d.y1},
                    x2: function(d) {return d.x2},
                    y2: function(d) {return d.y2},             
                    stroke: "green",
                    "stroke-linecap": "round"
                });

            var nodes = svg.selectAll("circle")
                .data([n1, n2, n3, n4, sr1, r1, r2, r3, r4, f1, f2, f3, f4, f5, f6, f7, f8, sr2, r5, f9]) 
                .enter()
                .append("circle")
                .attr({
                    "r": R,
                    "cx": function(d) {return d.x},
                    "cy": function(d) {return d.y},
                    "fill": function(d) {return d.fill},
                    "data-name": function(d) {return d.name},
                    "data-nextY": function(d) {return d.nextY},
                    "data-maxY": function(d) {return d.maxY}
                });


            var update = function(current, d, timespan) {
                current
                    .transition()
                    .duration(timespan)
                    .attr({
                        cx: d.x,
                        cy: d.y
                    });

                relations.filter(function(r) {
                        return r.source === d;
                    })
                    .transition()
                    .duration(timespan)
                    .attr({
                        x1: (Number.parseInt(d.x) + R),
                        y1: d.y
                    });

                relations.filter(function(r) {
                        return r.target === d; 
                    })
                    .transition()
                    .duration(timespan)
                    .attr({
                        x2: (Number.parseInt(d.x) - R),
                        y2: d.y
                    });
            }

            var start = {x: 0, y: 0};
            
            nodes
                .call(d3.behavior.drag()
                    .origin(function(d) {
                        return d;
                    })
                    .on("dragstart", function(d) { 
                        var current = d3.select(this);
                        start.x = current.attr("cx"); 
                        start.y = current.attr("cy");
                        d3.event.sourceEvent.stopPropagation(); // silence other listeners
                    })
                    .on("drag", function(d, i) {  
                        d.x = d3.event.x;
                        d.y = d3.event.y;
                        var current = d3.select(this);

                        update(current, d, 0);
                    })  
                    .on("dragend", function(d) {   
                        d.x = start.x;
                        d.y = start.y;
                        var current = d3.select(this);

                        update(current, d, 200);
                        d3.event.sourceEvent.stopPropagation(); // silence other listeners
                    })
                );
            
            
            
            var viewBox = {
                    x: 0,
                    y: 0
                },
                offset = {
                    x: 0,
                    y: 0
                },
                isMouseDown = false,
                oldScale = 1,
                width = 512,
                height = 1000,
                curPos_x = 0,
                curPos_y = 0;
            
            var padding = 15;
            
            svg.on("mousedown", function () {
                isMouseDown = true;
                offset.x = d3.mouse(this)[0];
                offset.y = d3.mouse(this)[1];
                console.log(offset.x - viewBox.x, offset.y - viewBox.y);
            });

            svg.on("mouseup", function () {
                isMouseDown = false;
                viewBox.x = viewBox.x - d3.mouse(this)[0] + offset.x;
                viewBox.y = viewBox.y - d3.mouse(this)[1] + offset.y;
                svg.attr("viewBox", viewBox.x + " " + viewBox.y + " " + width / oldScale + " " + height / oldScale);
            });

            svg.on("mousemove", function () {
                curPos_x = d3.mouse(this)[0];
                curPos_y = d3.mouse(this)[1];
                if (isMouseDown) {
                    viewBox.x = viewBox.x - d3.mouse(this)[0] + offset.x;
                    viewBox.y = viewBox.y - d3.mouse(this)[1] + offset.y;
                    svg.attr("viewBox", viewBox.x + " " + viewBox.y + " " + width / oldScale + " " + height / oldScale);
                    
//                     var check = {
//                         x: offset.x - viewBox.x,
//                         y: offset.y - viewBox.y
//                     };
//                     console.clear();
//                     console.log(check);
//                     if (check.x <= padding || check.x >= width - padding || check.y <= padding || check.y >= height - padding) {
//                         isMouseDown = false;
//                     }
                }
            });
            
            svg.call(
                d3.behavior.zoom()
                .scaleExtent([0.1, 20])
                .on("zoom", function () {
                    if (oldScale !== d3.event.scale) {
                        var scale = oldScale / d3.event.scale;
                        oldScale = d3.event.scale;
                        viewBox.x = curPos_x - scale * (curPos_x - viewBox.x);
                        viewBox.y = curPos_y - scale * (curPos_y - viewBox.y);
                        svg.attr("viewBox", viewBox.x + " " + viewBox.y + " " + width / oldScale + " " + height / oldScale);  
                     }
                })
            );
        }
        
        
        drawing();
        
        var arr= [1, 2, 3, 4, 5];
        d3.select("body").selectAll("text")
            .data(arr)
            .enter()
            .append("text")
            .text(1)
            .attr({
                fill: "white"
            });
    </script>
</body>
</html>